package demo.java.time;

import demo.java.time.format.DateTimeFormatterDemo;
import demo.vo.TimeRange;
import org.joda.time.DateTimeUtils;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.*;
import java.time.chrono.ChronoLocalDateTime;
import java.time.chrono.Chronology;
import java.time.chrono.HijrahChronology;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.time.temporal.IsoFields;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

/**
 * java8引入了一套全新的时间日期API，java。time包中的是类是不可变且线程安全的。 这与之前的Date与Calendar
 * API中的恰好相反，那里面像java.util.Date以及SimpleDateFormat这些关键的类都不是线程安全的。
 * <li>Instant——它代表的是时间戳
 * <li>LocalDate——不包含具体时间的日期，
 * <li>LocalTime——它代表的是不含日期的时间
 * <li>LocalDateTime——它包含了日期及时间，不过还是没有偏移信息或者说时区。
 * <li>ZonedDateTime——这是一个包含时区的完整的日期时间，偏移量是以UTC/格林威治时间为基准的。
 * <li>javax.time.ZoneId用来处理时区。时区指的是地球上共享同一标准时间的地区。每个时区都有一个唯一标识符，同时还有一个地区/城市(Asia/Tokyo)的格式以及从格林威治时间开始的一个偏移时间。比如说，东京的偏移时间就是+09:00。
 * <li>OffsetDateTime类实际上包含了LocalDateTime与ZoneOffset。它用来表示一个包含格林威治时间偏移量（+/-小时：分，比如+06:00或者-08：00）的完整的日期（年月日）及时间（时分秒，纳秒）。
 * <li>DateTimeFormatter类用于在Java中进行日期的格式化与解析。与SimpleDateFormat不同，它是不可变且线程安全的，如果需要的话，可以赋值给一个静态变量。
 * <p>
 * <p>
 * <h1>MySQL 时间类型：datetime 和 timestamp</h1>
 * <li>datetime：存储的是原始日期时间值，不包含时区信息，也不会进行时区转换。
 * 例如，插入 '2023-10-01 12:00:00' 时，数据库会原样存储，查询时也会返回相同的值，与数据库或应用程序的时区设置无关。
 * <li>timestamp：存储的是UTC 时间戳（从 1970-01-01 00:00:00 UTC 开始的秒数），会根据数据库的时区设置进行自动转换。
 * 范围从1970-01-01 至 2038-01-19（UTC），与时区相关，占用4 字节。
 * 当插入 '2023-10-01 12:00:00' 时，数据库会先将其转换为 UTC 时间（假设数据库时区为东八区 +8:00，则转换为 2023-10-01 04:00:00 UTC），再存储为时间戳。
 * 查询时，数据库会将 UTC 时间戳转换为当前时区的时间返回（即再次转换为 2023-10-01 12:00:00）。
 * <p><br>
 * <h1>LocalDateTime 和 OffsetDateTime 类的不同点：</h1>
 * <li>LocalDateTime 类不包含时区信息，仅包含年、月、日、时、分、秒、纳秒，
 * <li>OffsetDateTime 类包含时区偏移量的日期时间（如 2025-07-29T15:30:00+08:00），由 LocalDateTime 和 ZoneOffset（如 +08:00）组成。
 * 它能精确表示某个特定时区偏移下的时间，可用于跨时区的时间计算（如不同地区的时间转换）。
 * <li>LocalDateTime 类的时间范围是从 0000-01-01 00:00:00 到 9999-12-31 23:59:59，而 OffsetDateTime 类的时间范围是从 0000-01-01 00:00:00+00:00 到 9999-12-31 23:59:59+18:00。
 * <li>OffsetDateTime 包含日期、时间和时区偏移量（如 2023-10-01T12:30:00+08:00）
 */
public class DateTimeDemo {

    private static Logger logger = LoggerFactory.getLogger(DateTimeDemo.class);

    /**
     * 时间戳转时间
     *
     * @param date
     * @return
     */
    public static LocalDateTime toLocalDateTime(Date date) {
        if (date == null) {
            return null;
        }
        Instant instant = Instant.ofEpochMilli(date.getTime());
        ZonedDateTime zonedDateTime = instant.atZone(ZoneOffset.systemDefault());
        return zonedDateTime.toLocalDateTime();
    }


    /**
     * 将本地时间转换为 UTC 时间
     */
    public static LocalDateTime toUtc(LocalDateTime localTime, String localZoneId) {
        return localTime.atZone(ZoneId.of(localZoneId))
                .withZoneSameInstant(ZoneId.of("UTC"))
                .toLocalDateTime();
    }


    /**
     * 将本地时间转换为 UTC 时间
     */
    public static LocalDateTime toUtc(long ts) {
        return Instant.ofEpochMilli(ts)
                .atZone(ZoneId.of("UTC"))
                .toLocalDateTime();
    }


    /**
     * 时间戳转时间
     *
     * @param timestamp
     * @return
     */
    public static LocalDateTime toLocalDateTime(long timestamp) {
        Instant instant = Instant.ofEpochMilli(timestamp);
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.of("+8"));
        return offsetDateTime.toLocalDateTime();
    }

    /**
     * LocalDateTime 转Date
     *
     * @param localDateTime
     * @return
     */
    public static Date from(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return null;
        }
        Instant instant = localDateTime.toInstant(ZoneOffset.of("+8"));
        return Date.from(instant);
    }

    /**
     * LocalDate 转Date
     *
     * @param localDate
     * @return
     */
    public static Date from(LocalDate localDate) {
        if (localDate == null) {
            return null;
        }
        return from(LocalDateTime.of(localDate, LocalTime.MIN));
    }

    @Test
    public void testInstant() {
        Instant instant = Instant.now();
        System.out.println(instant.toEpochMilli() + " == " + System.currentTimeMillis());
        System.out.println(instant.toString());
        System.out.println(Instant.ofEpochMilli(System.currentTimeMillis()));
        System.out.println(DateTimeFormatter.ISO_INSTANT.format(Instant.now()));
    }

    @Test
    public void print() {
        System.out.println(DateTimeFormatterDemo
                .parse2Date("2023-10-26 12:30:00", DateTimeFormatterDemo.dateTimeFormatter).getTime());
        System.out.println(DateTimeFormatterDemo
                .parse2Date("2022-10-27 00:00:00", DateTimeFormatterDemo.dateTimeFormatter).getTime());
        Long[] timestamp = {1661151021654L};
        toLocalDateTime(1661151021654L);
        Arrays.asList(timestamp)
                .forEach(e -> System.out.println(DateTimeFormatterDemo.format(toLocalDateTime(e), DateTimeFormatter.ISO_LOCAL_DATE_TIME)));

        long now = System.currentTimeMillis();
        long ago2m = now - TimeUnit.MINUTES.toMillis(2);
        System.out.println(ago2m);
        System.out.println(now);
    }

    /**
     * 持续时长: 1小时40分钟
     *
     * @param start
     * @param end
     * @return
     */
    public static String formatDuration(long start, long end) {
        long seconds = (end - start) / 1000;
        long hours = seconds / 3600;
        long minutes = (seconds % 3600) / 60;
        long days = hours / 24;
        if (days > 0) {
            hours = hours % 24;
        }
        StringBuilder sb = new StringBuilder();
        if (days > 0) {
            sb.append(days).append("天");
        }
        if (hours > 0) {
            sb.append(hours).append("小时");
        }
        if (minutes > 0) {
            sb.append(minutes).append("分钟");
        }
        return sb.toString();
    }

    @Test
    public void test2LocalDateTime() {
        System.out.println(toLocalDateTime(1543973843675L));
    }

    @Test
    public void testTimeRange() {
        TimeRange t1 = new TimeRange(LocalDateTime.now().minusDays(2), LocalDateTime.now().plusDays(2));
        System.out.println(t1);
        TimeRange t2 = new TimeRange(LocalDateTime.now().minusDays(1), LocalDateTime.now().plusDays(1));
        System.out.println(t2);
        t2.merge(t1);
        System.out.println(t2);
    }

    @Test
    public void testLocalTime() {
        LocalTime localTime = LocalDateTime.now().toLocalTime();
        localTime = LocalTime.now();
        logger.info("{}", localTime);
        int secondOfDay = localTime.get(ChronoField.SECOND_OF_DAY);
        logger.info("secondOfDay = {}", secondOfDay);
        int hourOfDay = localTime.get(ChronoField.HOUR_OF_DAY);
        logger.info("hourOfDay = {}", hourOfDay);
        int minuteOfHour = localTime.get(ChronoField.MINUTE_OF_HOUR);
        logger.info("minuteOfHour = {}", minuteOfHour);
        int secondOfMinute = localTime.get(ChronoField.SECOND_OF_MINUTE);
        logger.info("secondOfMinute = {}", secondOfMinute);
        System.out.println(LocalTime.parse("10:15:30"));
        localTime = LocalTime.parse("10:15");
        logger.info("secondOfDay = {}", localTime.get(ChronoField.SECOND_OF_DAY));
        logger.info("{}.compare({}) = {}", LocalTime.parse("10:15:30"), LocalTime.parse("10:15"),
                LocalTime.parse("10:15:30").compareTo(LocalTime.parse("10:15")));
    }

    @Test
    public void untilDemo() {
        LocalDate localDate = LocalDate.parse("2019-04-28", DateTimeFormatter.ISO_LOCAL_DATE);
        long untilNowByYears = localDate.until(LocalDate.now(), ChronoUnit.YEARS);
        long untilNowByMonths = localDate.until(LocalDate.now(), ChronoUnit.MONTHS);
        long untilNowByDays = localDate.until(LocalDate.now(), ChronoUnit.DAYS);
        Period period = localDate.until(LocalDate.now());

        int years = period.getYears();
        int months = period.getMonths();
        int days = period.getDays();
        System.out.println(untilNowByYears + " years");
        System.out.println(untilNowByMonths + " months");
        System.out.println(untilNowByDays + "  days");
        System.out.println(years + " y " + months + " m " + days + " d ");
    }

    @Test
    public void demoLocalDate() {
        System.err.println(LocalDate.now().toString());
        LocalDate localDate = LocalDate.parse("1965-03-28", DateTimeFormatter.ISO_LOCAL_DATE);
        System.out.println(LocalDate.now().getYear());
        System.out.println(localDate);
        System.out.println(localDate.until(LocalDate.now(), ChronoUnit.DAYS));
        System.out.println(untilNowByDay(localDate));
        System.out.println(localDate.until(LocalDate.now(), ChronoUnit.YEARS));


        System.out.println("DayOfMonth = " + LocalDate.now().getDayOfMonth());
        System.out.println("WEEK_OF_YEAR = " + LocalDate.now().get(ChronoField.ALIGNED_WEEK_OF_YEAR));
    }

    /**
     * LocalDateTime、LocalDate、LocalTime
     * 提供了对java.util.Date的替代，另外还提供了新的DateTimeFormatter用于对格式化/解析的支持
     */
    @Test
    public void demoLocalDateTime() {
        // 使用默认时区时钟瞬时时间创建 Clock.systemDefaultZone() -->即相对于 ZoneId.systemDefault()默认时区
        LocalDateTime now = LocalDateTime.now();
        System.err.println(now);
        LocalDate _now = LocalDate.now();
        System.out.println(now + "是闰年吗？" + _now.isLeapYear());

        // 自定义时区
        LocalDateTime now2 = LocalDateTime.now(ZoneId.of("Europe/Paris"));
        System.out.println(now2);// 会以相应的时区显示日期

        // 自定义时钟
        Clock clock = Clock.system(ZoneId.of("Asia/Dhaka"));
        LocalDateTime now3 = LocalDateTime.now(clock);
        System.out.println(now3);// 会以相应的时区显示日期

        // 不需要写什么相对时间 如java.util.Date 年是相对于1900 月是从0开始
        // 2013-12-31 23:59
        LocalDateTime d1 = LocalDateTime.of(2013, 12, 31, 23, 59);
        System.out.println(d1);

        // 年月日 时分秒 纳秒
        LocalDateTime d2 = LocalDateTime.of(2013, 12, 31, 23, 59, 59, 11);
        System.out.println(d2);

        // 使用瞬时时间 + 时区
        Instant instant = Instant.now();
        LocalDateTime d3 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
        System.out.println(d3);

        // 解析String--->LocalDateTime
        LocalDateTime d4 = LocalDateTime.parse("2013-12-31T23:59");
        System.out.println(d4);

        LocalDateTime d5 = LocalDateTime.parse("2013-12-31T23:59:59.999");// 999毫秒 等价于999000000纳秒
        System.out.println(d5);

        // 使用DateTimeFormatter API 解析 和 格式化
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        LocalDateTime d6 = LocalDateTime.parse("2013/12/31 23:59:59", formatter);
        System.out.println(formatter.format(d6));

        // 时间获取
        System.out.println(d6.getYear());
        System.out.println(d6.getMonth());
        System.out.println(d6.getDayOfYear());
        System.out.println(d6.getDayOfMonth());
        System.out.println(d6.getDayOfWeek());
        System.out.println(d6.getHour());
        System.out.println(d6.getMinute());
        System.out.println(d6.getSecond());
        System.out.println(d6.getNano());
        // 时间增减
        LocalDateTime d7 = d6.minusDays(1);

        LocalDateTime d8 = d7.plus(1, IsoFields.QUARTER_YEARS);

        // LocalDate 即年月日 无时分秒
        LocalDate today = LocalDate.now();
        System.out.println(today);// yyyy-MM-dd
        LocalDate yesterday = LocalDate.now().minusDays(1);
        System.out.println(yesterday);// yyyy-MM-dd
        // LocalTime即时分秒 无年月日
        // API和LocalDateTime类似就不演示了
    }

    @Test
    public void testTransfer() {
        Date now = new Date();
        System.out.println(now.toString());
        System.out.println(toLocalDateTime(now));
        System.out.println(toLocalDateTime(now.getTime()));
    }

    /**
     * <li>如果目标对象为Null,抛NullPointerException
     */
    @Test
    public void testCompare() {
        int a = LocalDateTime.now().compareTo(null);
        System.out.println(a);
    }


    @Test
    public void demo() {
        System.err.println(LocalDate.now());
        LocalDateTime now = toLocalDateTime(System.currentTimeMillis());
        System.out.println(now);
        LocalDate localDate = LocalDate.parse("2018-02-24", DateTimeFormatter.ISO_LOCAL_DATE);
        System.out.println(untilByMonths(localDate, now.toLocalDate()));
        System.out.println(localDate.until(now, ChronoUnit.DAYS));

        // 20171202和20171206
        LocalDateTime t1 = LocalDateTime.of(2017, 12, 02, 23, 59, 59);
        LocalDateTime t2 = LocalDateTime.of(2017, 12, 06, 0, 0, 0);
        long dif = t2.toInstant(ZoneOffset.of("+8")).toEpochMilli() - t1.toInstant(ZoneOffset.of("+8")).toEpochMilli();
        System.out.println(dif / (24 * 3600 * 1000));

        LocalDateTime yesterday = now.minusDays(1);
        System.out.println("yesterday: " + yesterday);
        LocalDateTime start = LocalDateTime.of(yesterday.getYear(), yesterday.getMonth(), yesterday.getDayOfMonth(), 21,
                0);
        System.out.println("yesterday 21:00 : " + start);
        LocalDateTime end = start.plusHours(1);
        System.out.println("yesterday 22:00 : " + end + " Date=" + from(end));

        // 1 获取ZoneId
        System.err.println("== 获取ZoneId ==");
        System.out.println(ZoneId.getAvailableZoneIds());// [Asia/Aden, America/Cuiaba, ..., Europe/Monaco]
        System.out.println(ZoneId.systemDefault());// Asia/Shanghai
        System.out.println(ZoneDemo.ZONE_ID_SHANGHAI);// Asia/Shanghai
        //    System.out.println(ZoneId.of("Asia/xxx"));// 报错：java.time.zone.ZoneRulesException: Unknown time-zone ID: Asia/xxx
        System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.of("+8")));// UTC+08:00
        System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.of("Z")));// UTC

        System.out.println(ZoneId.from(ZonedDateTime.now()));// Asia/Shanghai
        System.out.println(ZoneId.from(ZoneOffset.of("+8")));// +08:00

        //    System.out.println(ZoneId.from(LocalDateTime.now()));// 只接受带时区的类型，LocalXXX不行，故报错：java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor:
        //    System.out.println(ZoneId.from(LocalDate.now()));// 只接受带时区的类型，LocalXXX不行，故报错：java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor:
        System.out.println();

        // 2 获取ZoneOffset
        System.err.println("== 获取ZoneOffset ==");
        System.out.println(ZoneOffset.MIN);// -18:00
        System.out.println(ZoneOffset.MAX);// +18:00
        System.out.println(ZoneOffset.UTC);// Z
        //    System.out.println(ZoneOffset.of("+20"));//报错：java.time.DateTimeException: Zone offset hours not in valid range: value 20 is not in the range -18 to 18

        System.out.println(ZoneOffset.ofHours(8));// +08:00
        System.out.println(ZoneOffset.ofHoursMinutes(8, 8));// +08:08
        System.out.println(ZoneOffset.ofHoursMinutesSeconds(8, 8, 8));// +08:08:08
        System.out.println(ZoneOffset.ofHours(-5));// -05:00
        System.out.println(ZoneOffset.ofTotalSeconds(8 * 60 * 60));// +08:00
        System.out.println();

        // 3 获取本地日期/时间，不带时区，会默认采用系统时区。LocalDate、LocalTime、LocalDateTime
        System.err.println("== 获取本地日期/时间，不带时区 ==");
        System.out.println(LocalDate.now());// 2021-03-01
        System.out.println(LocalTime.now());// 18:03:24.174
        System.out.println(LocalDateTime.now());// 2021-03-01T18:03:24.174
        System.out.println(LocalDateTime.of(2021, 3, 1, 10, 20));// 2021-03-01T10:20
        System.out.println(LocalDateTime.of(2021, 3, 1, 10, 20, 1));// 2021-03-01T10:20:01
        System.out.println();

        // 4 获取本地日期/时间，带时区。ZonedDateTime、OffsetDateTime
        System.err.println("== 获取本地日期/时间，带时区 ==");
        System.out.println(ZonedDateTime.now()); // 2021-03-01T18:03:24.175+08:00[Asia/Shanghai]
        System.out.println(ZonedDateTime.now(ZoneDemo.ZONE_ID_NEWYORK)); // 2021-03-01T05:03:24.203-05:00[America/New_York]
        System.out.println(ZonedDateTime.now(Clock.systemUTC())); // 2021-03-01T10:03:24.206Z
        System.out.println(ZonedDateTime.of(2021, 3, 1, 10, 20, 1, 0, ZoneDemo.ZONE_ID_NEWYORK));// 2021-03-01T10:20:01-05:00[America/New_York]

        System.out.println(OffsetDateTime.now()); // 2021-03-01T18:03:24.208+08:00
        System.out.println(OffsetDateTime.now(ZoneDemo.ZONE_ID_NEWYORK)); // 2021-03-01T05:03:24.208-05:00
        System.out.println(OffsetDateTime.now(Clock.systemUTC())); // 2021-03-01T10:03:24.208Z
        System.out.println(OffsetDateTime.of(2021, 3, 1, 10, 20, 1, 0, ZoneOffset.ofHours(8)));// 2021-03-01T10:20:01+08:00

        System.out.println();

        // 5 解析字符串日期或时间，分为带时区与不带时间的两种。LocalTime、ZonedDateTime、OffsetDateTime
        System.err.println("== 解析字符串日期或时间，分为带时区与不带时间的两种 ==");
        System.out.println(LocalDateTime.parse("2021-05-05T18:00"));// 2021-05-05T18:00
        System.out.println(LocalDateTime.parse("2021-05-05T18:00").atOffset(ZoneOffset.ofHours(8)));// 2021-05-05T18:00+08:00

        System.out.println(OffsetDateTime.parse("2021-05-05T18:00-04:00"));// 2021-05-05T18:00-04:00
        System.out.println(ZonedDateTime.parse("2021-05-05T18:00-05:00[America/New_York]"));// 2021-05-05T18:00-04:00[America/New_York]
        System.out.println();

        // 6 JSR310对日期时间的格式化/解析。java.time.format.DateTimeFormatter，线程安全
        System.err.println("== JSR310对日期时间的格式化/解析 ==");
        System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now()));// 2021-03-01
        System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalTime.now()));// 18:17:15.614
        System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()));// 2021-03-01T18:17:15.618

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("第Q季度 yyyy-MM-dd HH:mm:ss", Locale.US);
        System.out.println(formatter.format(LocalDateTime.now()));// 第1季度 2021-03-01 18:19:19
        System.out.println(formatter.parse("第1季度 2021-03-01 18:19:19", LocalDateTime::from));// 2021-03-01T18:19:19
        System.out.println(LocalDateTime.parse("第1季度 2021-03-01 18:19:19", formatter));// 2021-03-01T18:19:19

        System.out.println();

        // 7 计算相差的日期或时间长。Period、Duration
        System.err.println("== 计算相差的日期或时间长 ==");
        LocalDateTime localDateTime = LocalDateTime.of(2021, 3, 10, 10, 20);
        System.out.println(localDateTime);// 2021-03-10T10:20

        LocalDateTime afterLocalDateTime = localDateTime.plusMonths(1).plusDays(-3).minusHours(3);
        System.out.println(afterLocalDateTime);// 2021-04-07T07:20

        Period period = Period.between(localDateTime.toLocalDate(), afterLocalDateTime.toLocalDate());
        System.out.println(period.getMonths());// 0
        System.out.println(period.getDays());// 28

        Duration duration = Duration.between(localDateTime.toLocalTime(), afterLocalDateTime.toLocalTime());
        System.out.println(duration.toHours());// -3

        System.out.println();

        // 8日期或时间转换。LocalDateTime、OffsetDateTime、ZonedDateTime 之间
        System.err.println("== 日期或时间转换 ==");

        localDateTime = LocalDateTime.of(2021, 3, 1, 18, 0, 0);
        System.out.println(localDateTime);// 2021-03-01T18:00

        // 8.1 LocalDateTime to [OffsetDateTime、ZonedDateTime]
        OffsetDateTime offsetDateTime1 = localDateTime.atOffset(ZoneOffset.ofHours(8));
        OffsetDateTime offsetDateTime2 = OffsetDateTime.ofInstant(offsetDateTime1.toInstant(), ZoneOffset.ofHours(-5));
        System.out.println(offsetDateTime1);// 2021-03-01T18:00+08:00
        System.out.println(offsetDateTime2);// 2021-03-01T05:00-05:00

        ZonedDateTime zonedDateTime1 = localDateTime.atZone(ZoneDemo.ZONE_ID_SHANGHAI);
        ZonedDateTime zonedDateTime2 = ZonedDateTime.ofInstant(zonedDateTime1.toInstant(), ZoneDemo.ZONE_ID_NEWYORK);
        System.out.println(zonedDateTime1);// 2021-03-01T18:00+08:00[Asia/Shanghai]
        System.out.println(zonedDateTime2);// 2021-03-01T05:00-05:00[America/New_York]

        // 8.2 OffsetDateTime、ZonedDateTime间转换
        System.out.println(offsetDateTime1.toZonedDateTime());// 2021-03-01T18:00+08:00
        System.out.println(offsetDateTime1.atZoneSameInstant(ZoneDemo.ZONE_ID_NEWYORK));// 2021-03-01T05:00-05:00[America/New_York]
        System.out.println(offsetDateTime1.atZoneSimilarLocal(ZoneDemo.ZONE_ID_NEWYORK));// 2021-03-01T18:00-05:00[America/New_York]

        System.out.println(zonedDateTime1.toOffsetDateTime());// 2021-03-01T18:00+08:00

        // 8.3 [LocalDateTime, ZonedDateTime] to LocalDateTime
        System.out.println(offsetDateTime1.toLocalDateTime());// 2021-03-01T18:00
        System.out.println(zonedDateTime1.toLocalDateTime());// 2021-03-01T18:00

        // 8.4 不同zone间转换
        System.out.println(zonedDateTime1.withZoneSameInstant(ZoneDemo.ZONE_ID_NEWYORK));// 2021-03-01T05:00-05:00[America/New_York]
        System.out.println(zonedDateTime1.withZoneSameLocal(ZoneDemo.ZONE_ID_NEWYORK));// 2021-03-01T18:00-05:00[America/New_York]

    }

    /**
     * ISO-8601日历系统中与UTC偏移量有关的日期时间。
     * OffsetDateTime是一个带有偏移量的日期时间类型。
     * 存储有精确到纳秒的日期时间，以及偏移量。
     * 可以简单理解为 OffsetDateTime = LocalDateTime + ZoneOffset。
     * <p>
     * OffsetDateTime、ZonedDateTime和Instant它们三都能在时间线上以纳秒精度存储一个瞬间（请注意：LocalDateTime是不行的），也可理解某个时刻。
     * OffsetDateTime和Instant可用于模型的字段类型，因为它们都表示瞬间值并且还不可变，所以适合网络传输或者数据库持久化。
     * ZonedDateTime不适合网络传输/持久化，因为即使同一个ZoneId时区，不同地方获取到瞬时值也有可能不一样。
     */
    @Test
    public void offsetDateTimeDemo() {
        OffsetDateTime now = OffsetDateTime.now();

        Clock clock = Clock.systemDefaultZone();
        System.out.println("clock = " + clock);
        ZoneId systemZoneId = ZoneId.systemDefault();
        System.out.println("systemZoneId = " + systemZoneId);

        System.out.println("now() = " + OffsetDateTime.now() + " = " + OffsetDateTime.now(clock) + " = " + OffsetDateTime.now(systemZoneId));

        System.out.println("OffsetDateTime.atZoneSameInstant(UTC) = " + now.atZoneSameInstant(ZoneOffset.UTC));
        System.out.println("---------------");
        LocalDateTime localNow = LocalDateTime.now();
        System.out.println("LocalDateTime = " + localNow);

        OffsetDateTime offsetDateTime = localNow.atOffset(ZoneOffset.ofHours(8));
        System.out.println("LocalDateTime.atOffset(8) = " + offsetDateTime);

        offsetDateTime = localNow.atOffset(ZoneOffset.UTC);
        System.out.println("LocalDateTime.atOffset(UTC) = " + offsetDateTime);

        ZonedDateTime zonedDateTime = localNow.atZone(ZoneId.of("Asia/Shanghai"));
        System.out.println("LocalDateTime.atZone(\"Asia/Shanghai\") = " + zonedDateTime);
    }

    @Test
    public void testZonedDateTime() {
        ZonedDateTime zNow = ZonedDateTime.now();
        LocalDateTime lNow = LocalDateTime.now();
        OffsetDateTime oNow = OffsetDateTime.now();
        Instant iNow = Instant.now();
        long now = System.currentTimeMillis();
        System.out.println("Local = " + lNow);
        System.out.println("Zoned = " + zNow);
        System.out.println("Offset = " + oNow);
        System.out.printf("Instant = %s ，toEpochMilli=%s , ofEpochMilli=%s %n", iNow, iNow.toEpochMilli(), Instant.ofEpochMilli(now));
        System.out.println("currentTimeMillis = " + System.currentTimeMillis());

        OffsetDateTime offsetNow = zNow.toOffsetDateTime();
        System.out.println("ZonedDateTime.toOffsetDateTime() = " + offsetNow);

        // 将本地时间转换为 UTC 时间
        ZonedDateTime utcTime = lNow.atZone(ZoneId.of("Asia/Shanghai")).withZoneSameInstant(ZoneId.of("UTC"));
        System.out.println("将本地时间转换为 UTC 时间 = " + utcTime);

        // 将 UTC 时间转换为本地时间
        String utc = "2022-06-11T00:00:00.450Z";
        ZonedDateTime localTime = Instant.parse(utc).atZone(ZoneId.of("Asia/Shanghai"));
        System.out.printf("将 UTC 时间「%s」转换为本地时间[%s]%n", utc, localTime);

        System.out.println(toUtc(LocalDateTime.now(), "Asia/Shanghai"));
        System.out.println(toUtc(now));
    }


    /**
     * <h1>UTC时间</h1>
     * 概念：世界协调时间（Coordinated Universal Time，UTC）。
     * 它是以国际原子时（International Atomic Time，TAI，来自法国名字temps atomique International）作为计量单位的时间，计算结果极其严谨和精密。
     * 它比GMT时间更来得精准，误差值必须保持在0.9秒以内，倘若大于0.9秒就会通过闰秒来“解决”。
     * 1979年12月初内瓦举行的世界无线电行政大会通过决议，确定用“世界协调时间（UTC时间）”取代“格林威治时间（GMT时间）”，作为无线电通信领域内的国际标准时间。
     * <p>
     * 表示：UTC本地时间 = UTC标准时间 拼上 时间偏移量，偏移量有 ±[hh]:[mm]、±[hh][mm]、±[hh] 三种格式，
     * 如：若现在UTC时间是 10:30z（z表示偏移量=0，不可省略），则北京时间为 10:30 +0800、纽约时间为 10:30 -0500，分别表示同日下午6点半、同日上午五点半。
     * <p>
     * UTC时间里没有时区的概念，只有偏移量的概念，时间日期联盟组织对世界上主要的国家/地区定义了偏移量并给各偏移量取了对应的Time zone name。
     * GMT本地时间是由时区换算得到的、UCT本地时间是由附上偏移量得到的，两者有很大的相似性，但由于时区只有24个，
     * 因此GMT本地时间相比于GMT标准时间有24种情况、而UTC本地时间中偏移量有无数个故有无数种情况。
     * <p>
     * 由于有的国家在一年中会采用夏令时、冬令时两种计时制，故一个国家可能在不同的日期时有不同的偏移量，
     * 因此在使用 Java 中日期时间处理的JDK时最好通过zone name（类名为 java.name.ZoneId，值如 "Asia/Shanghai"）得到偏移量而非写死偏移量值，
     * 前者内部会自动处理有不同偏移量值的情况（java.time.zone.ZoneRules）、在当前日期得到对应的正确偏移值。
     */
    @Test
    public void testUTC() {
        // 获取ZoneId
        System.out.println(ZoneId.getAvailableZoneIds());// [Asia/Aden, America/Cuiaba, ..., Europe/Monaco]
        System.out.println(ZoneId.systemDefault());// Asia/Shanghai
        System.out.println(ZoneId.of("Asia/Shanghai"));// Asia/Shanghai
        //        System.out.println(ZoneId.of("Asia/xxx"));// 报错：java.time.zone.ZoneRulesException: Unknown time-zone ID: Asia/xxx
        System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.of("+8")));// UTC+08:00
        System.out.println(ZoneId.ofOffset("UTC", ZoneOffset.of("Z")));// UTC

        System.out.println(ZoneId.from(ZonedDateTime.now()));// Asia/Shanghai
        System.out.println(ZoneId.from(ZoneOffset.of("+8")));// +08:00

        //        System.out.println(ZoneId.from(LocalDateTime.now()));// 只接受带时区的类型，LocalXXX不行，故报错：java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor:
        //        System.out.println(ZoneId.from(LocalDate.now()));// 只接受带时区的类型，LocalXXX不行，故报错：java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor:
        System.out.println();

        // 获取ZoneOffset
        System.out.println(ZoneOffset.MIN);// -18:00
        System.out.println(ZoneOffset.MAX);// +18:00
        System.out.println(ZoneOffset.UTC);// Z
        //        System.out.println(ZoneOffset.of("+20"));//报错：java.time.DateTimeException: Zone offset hours not in valid range: value 20 is not in the range -18 to 18

        System.out.println(ZoneOffset.ofHours(8));// +08:00
        System.out.println(ZoneOffset.ofHoursMinutes(8, 8));// +08:08
        System.out.println(ZoneOffset.ofHoursMinutesSeconds(8, 8, 8));// +08:08:08
        System.out.println(ZoneOffset.ofHours(-5));// -05:00
        System.out.println(ZoneOffset.ofTotalSeconds(8 * 60 * 60));// +08:00
        System.out.println();

        // 获取本地日期/时间，不带时区。LocalTime
        System.out.println(LocalDate.now());// 2021-03-01
        System.out.println(LocalTime.now());// 18:03:24.174
        System.out.println(LocalDateTime.now());// 2021-03-01T18:03:24.174
        System.out.println();

        // 获取本地日期/时间，带时区。ZonedDateTime、OffsetDateTime
        System.out.println(ZonedDateTime.now()); // 2021-03-01T18:03:24.175+08:00[Asia/Shanghai]
        System.out.println(ZonedDateTime.now(ZoneId.of("America/New_York"))); // 2021-03-01T05:03:24.203-05:00[America/New_York]
        System.out.println(ZonedDateTime.now(Clock.systemUTC())); // 2021-03-01T10:03:24.206Z

        System.out.println(OffsetDateTime.now()); // 2021-03-01T18:03:24.208+08:00
        System.out.println(OffsetDateTime.now(ZoneId.of("America/New_York"))); // 2021-03-01T05:03:24.208-05:00
        System.out.println(OffsetDateTime.now(Clock.systemUTC())); // 2021-03-01T10:03:24.208Z
        System.out.println();

        // 解析字符串日期或时间，分为带时区与不带时间的两种。LocalTime、ZonedDateTime、OffsetDateTime
        System.out.println(LocalDateTime.parse("2021-05-05T18:00"));// 2021-05-05T18:00
        System.out.println(LocalDateTime.parse("2021-05-05T18:00").atOffset(ZoneOffset.ofHours(8)));// 2021-05-05T18:00+08:00

        System.out.println(OffsetDateTime.parse("2021-05-05T18:00-04:00"));// 2021-05-05T18:00-04:00
        System.out.println(ZonedDateTime.parse("2021-05-05T18:00-05:00[America/New_York]"));// 2021-05-05T18:00-04:00[America/New_York]
        System.out.println();

        // JSR310对日期时间的格式化/解析。java.time.format.DateTimeFormatter，线程安全
        System.out.println(DateTimeFormatter.ISO_LOCAL_DATE.format(LocalDate.now()));// 2021-03-01
        System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalTime.now()));// 18:17:15.614
        System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now()));// 2021-03-01T18:17:15.618

        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("第Q季度 yyyy-MM-dd HH:mm:ss", Locale.US);
        System.out.println(formatter.format(LocalDateTime.now()));// 第1季度 2021-03-01 18:19:19
        System.out.println(formatter.parse("第1季度 2021-03-01 18:19:19", LocalDateTime::from));// 2021-03-01T18:19:19
        System.out.println(LocalDateTime.parse("第1季度 2021-03-01 18:19:19", formatter));// 2021-03-01T18:19:19


    }

    /**
     * <li>LocalDateTime是一个不可变的日期-时间对象，它表示一个日期时间，通常被视为年-月-日-小时-分钟-秒。
     */
    @Test
    public void testLocalDateTime() {
        System.out.println("LocalDateTime最小值：" + LocalDateTime.MIN);
        System.out.println("LocalDateTime最大值：" + LocalDateTime.MAX);

        System.out.println("当前时区的本地时间：" + LocalDateTime.now());
        System.out.println("当前时区的本地时间：" + LocalDateTime.of(LocalDate.now(), LocalTime.now()));
        // 构造传入了ZoneId，并不是说LocalDateTime和时区有关了，而是告诉说这个Local指的是纽约
        System.out.println("纽约时区的本地时间：" + LocalDateTime.now(ZoneId.of("America/New_York")));
        LocalDateTime now = LocalDateTime.now(ZoneId.systemDefault());
        System.out.println("计算前：" + now);

        // 加3天
        LocalDateTime after = now.plusDays(3);
        // 减3个小时
        after = after.plusHours(-3); // 效果同now.minusDays(3);
        System.out.println("计算后：" + after);

        // 计算时间差
        Period period = Period.between(now.toLocalDate(), after.toLocalDate());
        System.out.println("相差天数：" + period.getDays());


        System.out.println("格式化输出（本地化输出，中文环境）：" +
                DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(now));

        String dateTimeStrParam = "2021-01-17 18:00:00";
        System.out.println("解析后输出：" + LocalDateTime.parse(dateTimeStrParam,
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.US)));
    }

    /**
     * Duration和Period
     * <p>
     * Duration类通过年月日时分秒相结合来描述一个时间量，最高精度是纳秒。
     * 时间量可以为正也可以为负，比如1天（86400秒0纳秒）、-1天（-86400秒0纳秒）、1年（31556952秒0纳秒）、1毫秒（0秒1000000纳秒）等。
     * <p>
     * Period类通过年月日相结合来描述一个时间量，最高精度是天。时间量可以为正也可以为负，例如2年（2年0个月0天）、3个月（0年3个月0天）、4天（0年0月4天）等。
     * <p>
     * 这两个类是不可变的、线程安全的、最终类。
     */
    @Test
    public void testDuration() {
        // 通过时间单位创建：ofDays(), ofHours(), ofMillis(), ofMinutes(), ofNanos(), ofSeconds()
        Duration fromDays = Duration.ofDays(1);
        System.out.println(fromDays.toString());

        // 通过LocalDateTime或LocalTime 创建
        LocalDateTime start = LocalDateTime.of(2022, 1, 1, 8, 0, 0);
        LocalDateTime end = LocalDateTime.of(2024, 5, 8, 8, 30, 30);
        Duration duration = Duration.between(start, end);
        System.out.println(duration.toString());

        // 通过已有的Duration 创建
        Duration duration2 = Duration.from(duration);
        System.out.println(duration2.toString());

        // 通过解析字符串创建：采用ISO-8601时间格式。格式为：PnYnMnDTnHnMnS   （n为个数）
        // P：开始标记, T：日期和时间的分割标记。“P”, “D”, “H”, “M” 和 “S”可以是大写或者小写（建议大写）；可以用“-”表示负数
        Duration fromChar1 = Duration.parse("P1DT1H10M10.5S");
        Duration fromChar2 = Duration.parse("PT10M");

//        "PT20.345S" -- parses as "20.345 seconds"
//        "PT15M"     -- parses as "15 minutes" (where a minute is 60 seconds)
//        "PT10H"     -- parses as "10 hours" (where an hour is 3600 seconds)
//        "P2D"       -- parses as "2 days" (where a day is 24 hours or 86400 seconds)
//        "P2DT3H4M"  -- parses as "2 days, 3 hours and 4 minutes"
//        "P-6H3M"    -- parses as "-6 hours and +3 minutes"
//        "-P6H3M"    -- parses as "-6 hours and -3 minutes"
//        "-P-6H+3M"  -- parses as "+6 hours and -3 minutes"

        // 比较两个时间的差
        Instant startTime = Instant.parse("2017-10-03T10:15:30.00Z");
        Instant endTime = Instant.parse("2017-10-03T10:16:30.00Z");

        Duration d1 = Duration.between(startTime, endTime);
        // 任何一个时间单元为负数，则返回true。true：end早于start
        d1.isNegative();
        // 得到相应的时差
        System.out.println(d1.toDays());
        System.out.println(d1.toHours());
        System.out.println(d1.toMinutes());
        System.out.println(d1.toMillis());
        System.out.println(d1.toNanos());

        // 增减方法
        Duration newDuration = duration.plusSeconds(33);
        Duration newDuration2 = duration.plus(33, ChronoUnit.SECONDS);

        LocalDateTime t1 = LocalDateTime.of(2023, 5, 29, 15, 00, 00);
        LocalDateTime t2 = LocalDateTime.now();
        System.out.println("t1: " + t1);
        System.out.println("t2: " + t2);
        Duration between = Duration.between(t1, t2);
        System.out.println("duration = " + between.toString());

        System.out.println("相差小时数：" + between.toHours());
        System.out.println("相差分钟数：" + between.toMinutes());
        long minutes = between.toMinutes();
        System.out.println("相差：" + minutes / 60 + "h " + minutes % 60 + "m");

        System.out.println("相差：" + formatDuration(from(t1).getTime(), from(t2).getTime()));
    }


    /**
     * 相差多少月
     *
     * @param start
     * @param end
     * @return
     */
    public static long untilByMonths(LocalDate start, LocalDate end) {
        return start.until(end, ChronoUnit.MONTHS);
    }

    /**
     * 相差多少天
     *
     * @param start
     * @param end
     * @return
     */
    public static long untilByDays(LocalDate start, LocalDate end) {
        return start.until(end, ChronoUnit.DAYS);
    }

    /**
     * 到现在相差多少天
     *
     * @param localDate
     * @return
     */
    public static long untilNowByDay(LocalDate localDate) {
        return untilByDays(localDate, LocalDate.now());
    }

    /**
     * 判断两个时间戳是否属于同一天
     *
     * @param d1
     * @param d2
     * @return
     */
    public static boolean isSameDay(Date d1, Date d2) {
        if (d1 == d2) {
            return true;
        }
        if (d1 == null || d2 == null) {
            return false;
        }
        if (d1.getTime() == d2.getTime()) {
            return true;
        }
        LocalDate dt1 = toLocalDateTime(d1.getTime()).toLocalDate();
        LocalDate dt2 = toLocalDateTime(d2.getTime()).toLocalDate();
        return dt1.equals(dt2);
    }

    @Test
    public void testTruncate() {
        LocalDateTime now = LocalDateTime.now();
        System.out.println(now);
        System.out.printf("truncatedToSECONDS: %s = %s %n", now.truncatedTo(ChronoUnit.SECONDS),
                from(now.truncatedTo(ChronoUnit.SECONDS)).getTime());
        System.out.println("truncatedTo(ChronoUnit.MINUTES) = " + now.truncatedTo(ChronoUnit.MINUTES));
        System.out.println("truncatedTo(ChronoUnit.HOURS) = " + now.truncatedTo(ChronoUnit.HOURS));
        System.out.println("truncatedTo(ChronoUnit.DAYS) = " + now.truncatedTo(ChronoUnit.DAYS));
        Instant instant = now.truncatedTo(ChronoUnit.MINUTES).toInstant(ZoneOffset.of("+8"));
        System.out.println(instant.toEpochMilli());
        System.out.println(System.currentTimeMillis());
    }

    /**
     * @param timestamp
     * @param unit
     * @return
     */
    public static long truncatedTo(long timestamp, ChronoUnit unit) {
        LocalDateTime localDateTime = toLocalDateTime(timestamp);
        localDateTime.truncatedTo(unit);
        return from(localDateTime).getTime();
    }


    /**
     * 判断当前是否在可用的时间区间
     * <li>如[21:00,09:00]表达的是晚9点到第二天的早9点
     * <li>如[09:00,21:00]表达的是早9点到晚9点
     *
     * @param start
     * @param end
     * @return
     */
    public static boolean isNowInTimeRange(LocalTime start, LocalTime end) {
        if (start == null || end == null) {
            return false;
        }
        LocalTime now = LocalTime.now();
        int compare = start.compareTo(end);
        // 如：09:00 < 21:00
        if (compare < 0) {
            return !now.isBefore(start) && !now.isAfter(end);
        } else if (compare > 0) {// 如 [21:00,09:00]，表达的是晚9点到第二天的早9点
            return !now.isBefore(start) && !now.isAfter(LocalTime.MAX)
                    || !now.isBefore(LocalTime.MIN) && !now.isAfter(end);
        } else {
            return now.equals(start);
        }
    }


    /**
     * 在java8中如何检查重复事件，比如生日？
     * 使用MonthDay类。这个类由月日组合，不包含年信息，可以用来代表每年重复出现的一些日期或其他组合。他和新的日期库中的其他类一样也都是不可变且线程安全的，并且它还是一个值类（value
     * class）
     */
    static void demoMonthDay() {
        MonthDay monthDay = MonthDay.now();
        System.out.println(monthDay);
        LocalDate birthday_nini = LocalDate.of(2016, 10, 1);
        System.out.println("Nini was born at " + birthday_nini);
        MonthDay birthday = MonthDay.of(birthday_nini.getMonth(), birthday_nini.getDayOfMonth());
        System.out.println("Nini's birthday is " + birthday);
        System.out.println("is today Nini's birthday ? " + birthday.equals(monthDay));
    }

    /**
     * Clock 时钟，类似于钟表的概念，提供了如系统时钟、固定时钟、特定时区的时钟。 可以用来获取某个时区下（所以对时区是敏感的）当前的瞬时时间、日期。
     * 用来代替System.currentTimelnMillis()与TimeZone.getDefault()方法。 时钟提供给我们用于访问某个特定 时区的
     * 瞬时时间、日期 和 时间的。
     */
    @Test
    public void demoClock() throws InterruptedException {
        // 系统默认UTC时钟（当前瞬时时间 System.currentTimeMillis()）
        Clock clock_SystemUTC = Clock.systemUTC();
        long start = clock_SystemUTC.millis();
        System.out.println("当前时间：" + new Date(start));
        Thread.sleep(100);
        // 每次调用将返回当前瞬时时间（UTC）
        System.out.println("耗时：" + (clock_SystemUTC.millis() - start));
        // 系统默认时区时钟（当前瞬时时间）
        Clock clock_systemDefaultZone = Clock.systemDefaultZone();
        System.out.println("系统默认时区时钟：" + clock_systemDefaultZone);
        // 巴黎时区
        Clock paris_clock = Clock.system(ZoneId.of("Europe/Paris"));
        // 每次调用将返回当前瞬时时间（UTC）
        System.out.printf("巴黎时钟：%s,时间戳：%d,转为Date：%s%n", paris_clock, paris_clock.millis(),
                new Date(paris_clock.millis()));
        // 上海时区
        Clock clock_shanghai = Clock.system(ZoneId.of("Asia/Shanghai"));
        // 每次调用将返回当前瞬时时间（UTC）
        System.out.printf("上海时钟：%s,时间戳：%d,转为Date：%s%n", clock_shanghai, clock_shanghai.millis(),
                new Date(clock_shanghai.millis()));
        // 固定上海时区时钟
        Clock clock_fixed = Clock.fixed(Instant.now(), ZoneId.of("Asia/Shanghai"));
        System.out.printf("固定了的时钟：%s,时间戳：%d %n", clock_fixed, clock_fixed.millis());
        Thread.sleep(1000);
        // 不变, 即时钟时钟在那一个点不动
        System.out.printf("固定了的时钟一秒后：%s,时间戳：%d %n", clock_fixed, clock_fixed.millis());
        // 相对于系统默认时钟两秒的时钟
        Clock clock_offset = Clock.offset(clock_SystemUTC, Duration.ofSeconds(10));

        System.out.printf("当前时钟时间戳：%d,相对于当前时钟10秒后的时间戳：%d %n", clock_SystemUTC.millis(), clock_offset.millis());
    }

    /**
     * LocalDate转Date
     */
    static void test2() {
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDate localDate = LocalDate.now();
        ZonedDateTime zdt = localDate.atStartOfDay(zoneId);

        Date date = Date.from(zdt.toInstant());

        System.out.println("LocalDate = " + localDate);
        System.out.println("Date = " + date);

    }

    /**
     * Date转LocalDate
     */
    static void test1() {
        Date date = new Date();
        Instant instant = date.toInstant();
        ZoneId zoneId = ZoneId.systemDefault();

        // atZone()方法返回在指定时区从此Instant生成的ZonedDateTime。
        LocalDate localDate = instant.atZone(zoneId).toLocalDate();
        System.out.println("Date = " + date);
        System.out.println("LocalDate = " + localDate);
    }

    /**
     * ZonedDateTime 带有时区的date-time 存储纳秒、时区和时差（避免与本地date-time歧义）；
     * API和LocalDateTime类似，只是多了时差(如2013-12-20T10:35:50.711+08:00[Asia/Shanghai])
     * ZonId代表的是某个特定时区，ZonedDateTime代表带时区的时间，等同于以前的GregorianCalendar类。使用该类，可以将本地时间转换成另一个时区中的对应时间。
     */
    public void demoZone() {
        LocalDateTime now = LocalDateTime.now();
        System.out.println("当前时间：" + now);

        ZoneId paris = ZoneId.of(ZoneId.SHORT_IDS.get("ECT"));
        ZonedDateTime paris_zonedDateTime = ZonedDateTime.of(now, paris);
        System.out.println("对应巴黎当前时间：" + paris_zonedDateTime);

        ZonedDateTime defaultZone = ZonedDateTime.now();
        System.out.println("默认时区：" + defaultZone);

        ZonedDateTime zonedDateTime_paris = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
        System.out.println("巴黎时区时间：" + zonedDateTime_paris);

        // 其他的用法也是类似的 就不介绍了
        ZonedDateTime z1 = ZonedDateTime.parse("2013-12-31T23:59:59Z[Europe/Paris]");
        System.out.println(z1);
    }

    /**
     * 用ZoneOffset来代表某个时区，可以使用它的静态方法ZoneOffset.of()方法来获取对应的时区，只要获得了这个偏移量，
     * 就可以用这个偏移量和LocalDateTime创建一个新的OffsetDateTime
     */
    @Test
    public void demoZoneOffset() {
        LocalDateTime now = LocalDateTime.now();
        ZoneOffset zoneOffset = ZoneOffset.ofHoursMinutes(5, 30);
        OffsetDateTime offsetDateTime = OffsetDateTime.of(now, zoneOffset);
        System.out.println(now);
        System.out.println(offsetDateTime);
    }


    /**
     * 将毫秒转化成 1h2m3s4ms 格式
     *
     * @param milliseconds
     * @return
     */
    public static String convertDuration(long milliseconds) {
        if (milliseconds < 1000) {
            return milliseconds + "ms";
        }
        long hours = TimeUnit.MILLISECONDS.toHours(milliseconds);
        long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60;
        long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60;
        long millis = milliseconds % 1000;
        StringBuilder sb = new StringBuilder();
        if (hours > 0) {
            sb.append(hours).append("h");
        }
        if (minutes > 0) {
            sb.append(minutes).append("m");
        }
        if (seconds > 0 || (hours == 0 && minutes == 0)) {
            sb.append(seconds).append("s");
        }
        if (millis > 0 || (hours == 0 && minutes == 0 && seconds == 0)) {
            sb.append(millis).append("ms");
        }
        return sb.toString();
    }

    /**
     * 将毫秒转化成 1h2m3s4ms 格式
     *
     * @param milliseconds
     * @return
     */
    public static String humanDuration(long milliseconds) {
        if (milliseconds < 10000) {
            return milliseconds + "ms";
        }
        long hours = TimeUnit.MILLISECONDS.toHours(milliseconds);
        long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60;
        long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60;
        StringBuilder sb = new StringBuilder();
        if (hours > 0) {
            sb.append(hours).append("h");
        }
        if (minutes > 0) {
            sb.append(minutes).append("m");
        }
        if (seconds > 0 || (hours == 0 && minutes == 0)) {
            sb.append(seconds).append("s");
        }
        return sb.toString();
    }

    /**
     * Epoch指的是一个特定的时间：1970-01-01 00:00:00 UTC。
     *
     * <h2>UTC</h2>
     * <p>
     * 协调世界时，UTC即Universal Time Coordinated的缩写。 在国际无线电通信场合，为了统一起见，使用一个统一的时间，称为UCT。
     * UCT与格林尼治平均时(GMT, Greenwich Mean
     * Time)一样，都与英国伦敦的本地时相同。整个地球分为二十四时区，每个时区都有自己的本地时间。
     * 时区差东为正，西为负。北京时区是东八区，领先UCT八个小时，即北京时间是UCT时间加上8小时。华盛顿处于西五区，华盛顿时间就是UCT时间减去5小时。
     * <h2>时间戳</h2>
     * <p>
     * java8获取时间戳特别简单。Instant类由一个静态的工厂方法now()可以返回当前时间戳。
     * 当前时间戳是包含日期和时间的，与java.util.Date很类似，事实上Instant就是java8以前的Date，
     * 可以使用这个两个类中的方法在这两个类型之间进行转换，比如Date.from(Instant)就是用来把Instant转换成java.util.date的，而Date。
     * toInstant()就是将Date转换成Instant的
     */
    @Test
    public void demoInstant() throws InterruptedException {
        System.out.println(Instant.now().toEpochMilli());
        System.out.println(System.currentTimeMillis());
        System.out.println(Calendar.getInstance().getTimeInMillis());
        System.out.println(new Date().getTime());

        Instant t1 = Instant.ofEpochMilli(System.currentTimeMillis());
        System.out.println(t1);

        System.out.println(Instant.now());
        Thread.sleep(500);
        System.out.println(Instant.now());

        Date now = Date.from(Instant.now());
        System.out.println(now);

        Instant instant = now.toInstant();
        System.out.println(instant);

        // 瞬时时间 相当于以前的System.currentTimeMillis()
        System.out.println(Instant.now().getEpochSecond());// 精确到秒
        System.out.println(Instant.now().toEpochMilli()); // 精确到毫秒

        Clock clock = Clock.systemUTC(); // 获取系统UTC默认时钟
        Instant instant2 = Instant.now(clock);// 得到时钟的瞬时时间
        System.out.println(instant2.toEpochMilli());

        // 固定瞬时时间时钟
        Clock fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault());
        Instant instant3 = Instant.now(fixedClock);// 得到时钟的瞬时时间
        System.out.println(instant3.toEpochMilli());// equals instant1

        Instant current = Clock.system(ZoneId.of("Asia/Shanghai")).instant();
        System.out.println(current.toEpochMilli() + "      " + current);
    }

    /**
     * Chronology 用于对年历系统的支持，是java.util.Calendar的替代者
     */
    static void demoChronology() {
        // 提供对java.util.Calendar的替换，提供对年历系统的支持
        Chronology c = HijrahChronology.INSTANCE;
        ChronoLocalDateTime d = c.localDateTime(LocalDateTime.now());
        System.out.println(d);
    }


    /**
     * Hours per day.
     */
    public static final int HOURS_PER_DAY = 24;
    /**
     * Minutes per hour.
     */
    public static final int MINUTES_PER_HOUR = 60;
    /**
     * Seconds per minute.
     */
    public static final int SECONDS_PER_MINUTE = 60;
    /**
     * Seconds per hour.
     */
    public static final int SECONDS_PER_HOUR = SECONDS_PER_MINUTE * MINUTES_PER_HOUR;
    /**
     * Seconds per day.
     */
    public static final int SECONDS_PER_DAY = SECONDS_PER_HOUR * HOURS_PER_DAY;
    /**
     * Milliseconds per day.
     */
    public static final long MILLIS_PER_DAY = SECONDS_PER_DAY * 1000L;


    /**
     * 计算两个日期之间包含多少天、周、月、年。可以用java.time.Period类完成该功能。
     * Period类通过年月日相结合来描述一个时间量，最高精度是天。时间量可以为正也可以为负，例如2年（2年0个月0天）、3个月（0年3个月0天）、4天（0年0月4天）等。
     */
    @Test
    public void testPeriod() {

        LocalDate birthday_nini = LocalDate.of(2016, 10, 1);
        LocalDate now = LocalDate.now();
        Period period = Period.between(birthday_nini, now);
        System.out.printf("%s 和 %s 相差 %d 年 %d 月  %d 天。", birthday_nini, now, period.getYears(), period.getMonths(),
                period.getDays());

        // 通过时间单位创建：如果仅一个值表示，如使用ofDays()方法，那么其他值为0。
        Period fromUnits = Period.of(3, 10, 10);
        Period fromDays = Period.ofDays(50);
        Period fromMonths = Period.ofMonths(5);
        Period fromYears = Period.ofYears(10);
        Period fromWeeks = Period.ofWeeks(40);  //280天

        // 通过LocalDate创建
        LocalDate startDate = LocalDate.of(2015, 2, 20);
        LocalDate endDate = LocalDate.of(2017, 1, 15);
        // startDate减endDate
        period = Period.between(startDate, endDate);
        boolean negative = period.isNegative();
        System.out.println(negative ? "endDate早于startDate" : "endDate晚于于startDate");

        // 解析方法 : 格式1：“PnYnMnWnD”  P：开始符，表示period（即：表示年月日）；P, Y, M, W, D都可以用大写或者小写。
        Period period1 = Period.parse("P2Y");       //2年
        Period period2 = Period.parse("P2Y3M5D");   //2年3月5天
        Period period3 = Period.parse("P1Y2M3W4D"); // 1年2月3周4天。即：1年2月25天

        // 获得年月日
        int years = period3.getYears();
        int months = period3.getMonths();
        int days = period3.getDays();

        System.out.println(period3);
        System.out.println(String.format("%s years,%s months,%s days", years, months, days));

        //增减方法
        Period period4 = period3.plusDays(4);
        period4 = period4.minusMonths(1);
        System.out.println(String.format("%s years,%s months,%s days", period4.getYears(), period4.getMonths(), period4.getDays()));

        // 转换单位
        long totalMonths = period3.toTotalMonths();
        System.out.println("totalMonths = " + totalMonths);
    }

    @Test
    public void testYearMonthDay() {
        Year year = Year.now();
        YearMonth yearMonth = YearMonth.now();
        MonthDay monthDay = MonthDay.now();

        System.out.println(year);// 年
        System.out.println(yearMonth); // 年-月
        System.out.println(monthDay); // 月-日

        yearMonth = YearMonth.of(2018, Month.FEBRUARY);
        System.out.println(yearMonth);

        YearMonth now = YearMonth.now();
        System.out.printf("%s 这个月有%d天.", now.toString(), now.lengthOfMonth());
    }

}
